import os
import pip
import argparse


def log_print(level, text):
    # Log level colors
    LEVEL_COLORS = {
        'error': '\033[31m',
        'success': '\033[32m',
        'warning': '\033[33m',
        'info': '\033[34m',
    }
    RESET_COLOR = '\033[0m'
    # Log level name
    LEVEL_NAME = {
        'error': 'ERROR',
        'success': 'SUCCESS',
        'warning': 'WARNING',
        'info': 'INFO',
    }
    print(LEVEL_COLORS[level] + LEVEL_NAME[level] + ': ' + text + RESET_COLOR)


def install_package(package):
    log_print('info', "%s package installing..." % package)
    pip.main(['install', package])


try:
    from lxml import etree
except ImportError:
    install_package('lxml')
    from lxml import etree


class MDK5:

    def __init__(self, mdk_path):
        # Get MDK project file
        mdk_file = []
        for root, dirs, fs in os.walk(mdk_path):
            for f in fs:
                if f.endswith('.uvprojx'):
                    mdk_file = os.path.join(root, f)
                    break
            if mdk_file:
                break
        # Check mdk file, init self
        if mdk_file:
            self.path = os.path.dirname(mdk_file)
            self.file = mdk_file
            self.tree = etree.parse(mdk_file)
            self.root = self.tree.getroot()
        else:
            log_print('error', "MDK build failed, '.uvprojx' file not found")
            exit(1)

    def add_include_path(self, path):
        # Fix path
        path = os.path.relpath(path, self.path).replace('\\', '/')
        # Add path
        inc_path = self.tree.xpath("//Cads/VariousControls/IncludePath")[0]
        exist_paths = inc_path.text.split(';')
        if path not in exist_paths:
            inc_path.text += f";{path}"
        log_print('info', "include %s" % path)

    def add_include_paths(self, paths):
        for path in paths:
            self.add_include_path(path)

    def add_files_new_group(self, name, files):
        # Fix name and files
        name = name.replace('\\', '/')
        fix_files = []
        for file in files:
            file = os.path.relpath(file, self.path).replace('\\', '/')
            fix_files.append(file)
        files = fix_files
        # Add group
        groups_node = self.tree.find('//Groups')
        group_node = groups_node.find(f"./Group[GroupName='{name}']")
        if group_node is None:
            group_node = etree.SubElement(groups_node, "Group")
            group_name_node = etree.SubElement(group_node, "GroupName")
            group_name_node.text = name
        # Check files
        if files is None:
            return
        # Add files
        files_node = group_node.find("Files")
        if files_node is None:
            files_node = etree.SubElement(group_node, "Files")
        for file in files:
            # Add file
            file_node = files_node.find(f"./File[FileName='{os.path.basename(file)}']")
            if file_node is None:
                file_node = etree.SubElement(files_node, "File")
            file_name_node = file_node.find(f"./FileName")
            if file_name_node is None:
                file_name_node = etree.SubElement(file_node, "FileName")
            file_type_node = file_node.find(f"./FileType")
            if file_type_node is None:
                file_type_node = etree.SubElement(file_node, "FileType")
            file_path_node = file_node.find(f"./FilePath")
            if file_path_node is None:
                file_path_node = etree.SubElement(file_node, "FilePath")
            file_name_node.text = os.path.basename(file)
            file_path_node.text = file
            file_type_map = {
                '.c': '1',
                '.s': '2',
                '.o': '3',
                '.lib': '4',
                '.h': '5',
                '.cpp': '8'
            }
            file_extension = os.path.splitext(file_name_node.text)[1]
            file_type = file_type_map.get(file_extension, '9')
            file_type_node.text = file_type
        log_print('info', "add %s" % name)

    def add_path_files(self, path):
        files = []
        for root, dirs, fs in os.walk(path):
            for f in fs:
                files.append(os.path.relpath(os.path.join(root, f), self.path))
        self.add_files_new_group(path, files)

    def add_path_c_files(self, path):
        # Get c files
        files = []
        for root, dirs, fs in os.walk(path):
            if root == path:
                for f in fs:
                    if f.endswith(".c") or f.endswith(".cpp") or f.endswith(".cxx"):
                        file = os.path.relpath(os.path.join(root, f), self.path)
                        files.append(file)
        # Fix name
        name = os.path.relpath(path, self.path).replace('\\', '/').replace("../", "")
        # Add group
        if files:
            self.add_files_new_group(name, files)

    def use_gnu(self, enable=True):
        # Check uAC6
        ac6_node = self.tree.find('//Target/uAC6')
        # Use GNU
        if ac6_node.text == '0':
            # Get uGnu
            gnu_node = self.tree.find('//Cads/uGnu')
            gnu_text = '1' if enable else '0'
        else:
            # Get gnu-c99
            gnu_node = self.tree.find('//Cads/v6Lang')
            gnu_text = '4' if enable else '3'
        # Set gnu
        if gnu_node is not None:
            if gnu_node.text != gnu_text:
                gnu_node.text = gnu_text
                log_print('info', "use GNU")

    def save(self):
        self.tree.write(self.file, pretty_print=True, encoding="utf-8", xml_declaration=True)
        log_print('success', "MDK %s build success" % self.file)


def build_mdk(source_paths, include_paths, gnu=False):
    # Get MDK project file
    mdk_path = os.path.dirname(__file__)
    mdk_proj = MDK5(mdk_path)
    # Add sources
    if source_paths:
        for source_path in source_paths:
            mdk_proj.add_path_c_files(source_path)
    # Add includes
    if include_paths:
        mdk_proj.add_include_paths(include_paths)
    # Use GNU
    if gnu:
        mdk_proj.use_gnu(True)
    # Save
    mdk_proj.save()


def show_logo():
    print(" __  __                  _   _   _                                 ")
    print("|  \/  |  _ __          | | (_) | |__    _ __    __ _   _ __   _   _")
    print("| |\/| | | '__|  _____  | | | | | '_ \  | '__|  / _` | | '__| | | | |")
    print("| |  | | | |    |_____| | | | | | |_) | | |    | (_| | | |    | |_| |")
    print("|_|  |_| |_|            |_| |_| |_.__/  |_|     \__,_| |_|     \__, |")
    print("                                                               |___/")


if __name__ == '__main__':
    show_logo()

    # Parse arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("-mdk", "--mdk", action="store_true", help="Build MDK")
    parser.add_argument("-s", "--sources", nargs='+', help="Add path sources")
    parser.add_argument("-i", "--includes", nargs='+', help="Add include paths")
    parser.add_argument("-g", "--gnu", action="store_true", help="Use GNU")
    args = parser.parse_args()

    # MDK
    if args.mdk:
        args = parser.parse_args()
        build_mdk(args.sources, args.includes, args.gnu)
